package edu.vcu.csl.ess;

import edu.vcu.csl.ess.RawModel;
import edu.vcu.csl.ess.RawModel.Reaction;

import java.util.ArrayList;

class RawModelCheck {
	public static boolean isValid(RawModel model) {
		int errorCount = 0;

		errorCount += checkForSpecies(model);
		errorCount += checkTimeParameters(model);
		errorCount += checkForValidAndUniqueNames(model);
		errorCount += checkReactionEquations(model);
		errorCount += checkEventTimes(model);

		return (errorCount == 0);
	}

	public static int checkTimeParameters(RawModel model) {
		int errorCount = 0;

		if (model.start < 0.0) { 
			model.errors.add("Start time is negative.");
			errorCount++;
		}

		if (model.end <= 0.0) {
			model.errors.add("End time must be greater than zero.");
			errorCount++;
		}

		if (model.end <= model.start) {
			model.errors.add("End time must be greater than the start time.");
			errorCount++;
		} 

		if (model.interval <= 0.0) {
			model.errors.add("Print interval must be greater than zero.");
			errorCount++;
		}

		return errorCount;
	}

	public static int checkForSpecies(RawModel model) {
		int errorCount = 0;

		if (model.species.size() == 0) {
			model.errors.add("No species exist in the model.");
			errorCount++;
		}

		return errorCount;
	}

	public static int checkForValidAndUniqueNames(RawModel model) {
		ArrayList<String> names = new ArrayList<String>();
		int errorCount = 0;

		for (RawModel.Constant s : model.constants) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		for (RawModel.Species s : model.species) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		for (RawModel.Reaction s : model.reactions) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		for (RawModel.Event s : model.events) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		for (RawModel.Periodic s : model.periodics) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		for (RawModel.Trigger s : model.triggers) {
			if (!JavaCodeParser.isValidName(s.name)) {
				errorCount++;
				s.error = "Invalid name.";
			} else {
				if (isStringInArrayList(s.name,names)) {
					errorCount++;
					s.error = "Duplicate name.";
				} else {
					names.add(s.name);
				}
			}
		}

		return errorCount;
	}

	public static int checkReactionEquations(RawModel model) {
		int errorCount = 0;

		for (RawModel.Reaction r : model.reactions) {
			ReactionParser.parse(r);
			if (r.error == null) {
				for(RawModel.ReactionElement e : r.reactants) {
					boolean valid = false;
					for(RawModel.Species s : model.species) {
						if (s.name.compareTo(e.species) == 0) {
							valid = true;
							break;
						}
					}
					if (!valid) {
						r.error = "Unknown reactant '" + e.species + "'.";
						errorCount++;
					}
				}
				for(RawModel.ReactionElement e : r.products) {
					boolean valid = false;
					for(RawModel.Species s : model.species) {
						if (s.name.compareTo(e.species) == 0) {
							valid = true;
							break;
						}
					}
					if (!valid) {
						r.error = "Unknown product '" + e.species + "'.";
						errorCount++;
					}
				}
			}
		}

		return errorCount;
	}

	public static int checkEventTimes(RawModel model) {
		int errorCount = 0;

		for (RawModel.Event e : model.events) {
			if (Double.parseDouble(e.time) < 0) {
				e.error = "Event time must be greater than 0.";
				errorCount++;
			}
		}

		for (RawModel.Periodic p : model.periodics){
			if (Double.parseDouble(p.period) <= 0){
				p.error = "Period must be greater than 0.";
				errorCount++;
			}
			if (Double.parseDouble(p.start) < 0){
				p.error = "Start time must not be negative.";
				errorCount++;
			}
			if (Double.parseDouble(p.end) < 0) {
				p.error = "End time must be greater than 0.";
				errorCount++;
			}
			if (Double.parseDouble(p.end) < Double.parseDouble(p.start)) {
				p.error = "Start time must be less than end time.";
				errorCount++;
			}
		}

		return errorCount;
	}

	public static boolean isStringInArrayList(String str, ArrayList<String> list) {
		for(String s : list) {
			if (str.compareTo(s) == 0) {
				return true;
			}
		}
		return false;
	}
}